/**
 * Copyright 2010 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package saig.engine.utils;

import java.awt.Point;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import saig.engine.Level;
import saig.engine.models.Spawn;
import saig.engine.models.Tile;
import saig.engine.models.enums.ModelType;
import saig.engine.resources.TileLoader;


/**
 * Utility class that reads tile information and loads the tiles.
 * @author Adam
 * @version $Id$
 * 
 * Created on Jun 20, 2010 at 4:16:47 PM 
 */
public final class LevelUtil {
	/**
	 * Logger.
	 */
	final static Logger LOGGER = LoggerFactory.getLogger(LevelUtil.class);
	/**
	 * Private Constructor.
	 */
	private LevelUtil() { }
	/**
	 * Loads a level from a file.
	 * @param file The file containing the level information.
	 * @param tiles A list of all the game tiles.
	 * @return A Level or null.
	 */
	public static Level loadLevel(File file) {
		try {
			LOGGER.debug("Loading file [{}].", file);
			return loadLevel(new FileInputStream(file));
		} catch (final FileNotFoundException e) {
			LOGGER.error("Unable to load level.", e);
		}
		return null;
	}
	/**
	 * Loads a level from an input stream.
	 * @param iStream The input stream containing the level information.
	 * @param tiles A list of all the game tiles.
	 * @return A Level or null.
	 */
	public static Level loadLevel(InputStream iStream) {
		Level level = null;
		final Map<Integer, Tile> tileMap = new HashMap<Integer,Tile>();
		for (Tile tile : TileLoader.getInstance().getTiles()) {
			tileMap.put(tile.getId(), tile);
		}
		try {
			final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			final DocumentBuilder db = dbf.newDocumentBuilder();
			final Document doc = db.parse(iStream);
			doc.getDocumentElement().normalize();
			Element levelEl = doc.getDocumentElement();
			int width = Integer.valueOf(levelEl.getAttribute("width"));
			int height = Integer.valueOf(levelEl.getAttribute("height"));
			int tileWidth = Integer.valueOf(levelEl.getAttribute("tileWidth"));
			int tileHeight = Integer.valueOf(levelEl.getAttribute("tileHeight"));
			// Create the level.
			level = new Level(width, height, tileWidth, tileHeight);
			if (levelEl.getAttribute("startX") != null && levelEl.getAttribute("startX").length() > 0) {
				int startX = Integer.valueOf(levelEl.getAttribute("startX"));
				int startY = Integer.valueOf(levelEl.getAttribute("startY"));
				level.setStart(new Point(startX, startY));
			}
			if (levelEl.getAttribute("endX") != null && levelEl.getAttribute("endX").length() > 0) {
				int endX = Integer.valueOf(levelEl.getAttribute("endX"));
				int endY = Integer.valueOf(levelEl.getAttribute("endY"));
				level.setEnd(new Point(endX, endY));
			}
			final NodeList tileNodes = doc.getElementsByTagName("tile");
			for (int i = 0; i < tileNodes.getLength(); i++) {
				final Node node = tileNodes.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					final Element tileEl = (Element) node;
					final Tile tile = tileMap.get(Integer.valueOf(tileEl.getAttribute("id")));
					final int x = Integer.valueOf(tileEl.getAttribute("x"));
					final int y = Integer.valueOf(tileEl.getAttribute("y"));
					level.setTile(x, y, tile);
				}
			}
			final NodeList spawnNodes = doc.getElementsByTagName("spawn");
			for (int i = 0; i < spawnNodes.getLength(); i++) {
				final Node node = spawnNodes.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					final Element spawnEl = (Element) node;
					final int x = Integer.valueOf(spawnEl.getAttribute("x"));
					final int y = Integer.valueOf(spawnEl.getAttribute("y"));
					final int type = Integer.valueOf(spawnEl.getAttribute("type"));
					final int color = Integer.valueOf(spawnEl.getAttribute("color"));
					final boolean wings = Boolean.valueOf(spawnEl.getAttribute("wings"));
					level.setSpawn(new Spawn(ModelType.fromInt(type), wings, color, x, y));
				}
			}
		} catch (final ParserConfigurationException parserEx) {
			LOGGER.error("Parser Exception when loading the level.", parserEx);
		} catch (SAXException saxEx) {
			LOGGER.error("Parsing Exception when loading the level.", saxEx);
		} catch (IOException ioEx) {
			LOGGER.error("I/O Exception when loading the level.", ioEx);
		}
		return level;
	}
	public static void saveLevel(File file, Level level) {
		XMLStreamWriter writer = null;
		try {
			XMLOutputFactory factory = XMLOutputFactory.newInstance();
			writer = factory.createXMLStreamWriter(new FileOutputStream(file), "UTF-8");
			writer.writeStartDocument("UTF-8", "1.0");
			writer.writeStartElement("level");
			writer.writeAttribute("width", String.valueOf(level.getWidth()));
			writer.writeAttribute("height", String.valueOf(level.getHeight()));
			writer.writeAttribute("tileWidth", String.valueOf(level.getTileWidth()));
			writer.writeAttribute("tileHeight", String.valueOf(level.getTileHeight()));
			if (level.getStart() != null) {
				writer.writeAttribute("startX", String.valueOf((int) level.getStart().getX()));
				writer.writeAttribute("startY", String.valueOf((int) level.getStart().getY()));
			}
			if (level.getEnd() != null) {
				writer.writeAttribute("endX", String.valueOf((int) level.getEnd().getX()));
				writer.writeAttribute("endY", String.valueOf((int) level.getEnd().getY()));
			}
			writer.writeStartElement("tiles");
			for (int x = 0; x < level.getWidth(); x++) {
				for (int y = 0; y < level.getHeight(); y++) {
					final Tile tile0 = level.getTile(x, y);
					if (tile0 != null) {
						writer.writeStartElement("tile");
						writer.writeAttribute("x", String.valueOf(x));
						writer.writeAttribute("y", String.valueOf(y));
						writer.writeAttribute("id", String.valueOf(tile0.getId()));
						writer.writeEndElement(); // Tile
					}
				}
			}
			writer.writeEndElement(); // Tiles
			writer.writeStartElement("spawns");
			for (int r = 0; r < level.getPixelWidth(); r+= level.getRegionSize()) {
				for (Spawn spawn : level.getSpawns(r)) {
					writer.writeStartElement("spawn");
					writer.writeAttribute("x", String.valueOf(spawn.getX()));
					writer.writeAttribute("y", String.valueOf(spawn.getY()));
					writer.writeAttribute("type", String.valueOf(spawn.getType().toInt()));
					writer.writeAttribute("typeName", spawn.getType().toString());
					writer.writeAttribute("color", String.valueOf(spawn.getColor()));
					writer.writeAttribute("wings", String.valueOf(spawn.isWings()));
					writer.writeEndElement(); // Spawn
				}
			}
			writer.writeEndElement(); // Spawns
			writer.writeEndElement(); // Level
			writer.writeEndDocument();
		} catch (final FileNotFoundException e) {
			LOGGER.error("File not found.", e);
		} catch (final XMLStreamException e) {
			LOGGER.error("XML error occured saving the level.", e);
		} finally {
			if (writer != null) {
				try {
					writer.flush();
					writer.close();
				} catch (final XMLStreamException e) {
					LOGGER.error("XML error occured saving the level.", e);
				}
			}
		}
	}
}
